package com.chenqq.camerademo.camera.es;


import android.graphics.Canvas;
import android.graphics.Rect;
import android.opengl.GLES10;
import android.opengl.GLES20;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;


import com.chenqq.camerademo.camera.utils.BuildCheck;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;


public class EGLBase10
        extends EGLBase {
    private static final String TAG = "EGLBase10";
    private EGL10 mEgl = null;
    private EGLDisplay mEglDisplay = null;
    private Config mEglConfig = null;
    private int mGlVersion = 2;

    private static final Context EGL_NO_CONTEXT = new Context(EGL10.EGL_NO_CONTEXT);
    @NonNull
    private Context mContext = EGL_NO_CONTEXT;


    public static class Context
            extends IContext {
        public final EGLContext eglContext;


        private Context(EGLContext context) {
            this.eglContext = context;
        }
    }

    public static class Config
            extends IConfig {
        public final EGLConfig eglConfig;

        private Config(EGLConfig eglConfig) {
            this.eglConfig = eglConfig;
        }
    }


    public static class MySurfaceHolder
            implements SurfaceHolder {
        private final Surface surface;


        public MySurfaceHolder(Surface surface) {
            this.surface = surface;
        }


        public Surface getSurface() {
            return this.surface;
        }


        public void addCallback(Callback callback) {
        }


        public void removeCallback(Callback callback) {
        }


        public boolean isCreating() {
            return false;
        }


        public void setType(int type) {
        }


        public void setFixedSize(int width, int height) {
        }


        public void setSizeFromLayout() {
        }


        public void setFormat(int format) {
        }


        public void setKeepScreenOn(boolean screenOn) {
        }


        public Canvas lockCanvas() {
            return null;
        }


        public Canvas lockCanvas(Rect dirty) {
            return null;
        }


        public void unlockCanvasAndPost(Canvas canvas) {
        }


        public Rect getSurfaceFrame() {
            return null;
        }
    }


    public static class EglSurface
            implements IEglSurface {
        private final EGLBase10 mEglBase;

        private EGLSurface mEglSurface = EGL10.EGL_NO_SURFACE;


        private EglSurface(EGLBase10 eglBase, Object surface) throws IllegalArgumentException {
            this.mEglBase = eglBase;
            if (surface instanceof Surface && !BuildCheck.isAndroid4_2()) {


                this.mEglSurface = this.mEglBase.createWindowSurface(new MySurfaceHolder((Surface) surface));
            } else if (surface instanceof Surface || surface instanceof SurfaceHolder || surface instanceof android.graphics.SurfaceTexture || surface instanceof android.view.SurfaceView) {


                this.mEglSurface = this.mEglBase.createWindowSurface(surface);
            } else {
                throw new IllegalArgumentException("unsupported surface");
            }
        }


        private EglSurface(EGLBase10 eglBase, int width, int height) {
            this.mEglBase = eglBase;
            if (width <= 0 || height <= 0) {
                this.mEglSurface = this.mEglBase.createOffscreenSurface(1, 1);
            } else {
                this.mEglSurface = this.mEglBase.createOffscreenSurface(width, height);
            }
        }


        public void makeCurrent() {
            this.mEglBase.makeCurrent(this.mEglSurface);
            if (this.mEglBase.getGlVersion() >= 2) {
                GLES20.glViewport(0, 0, this.mEglBase.getSurfaceWidth(this.mEglSurface), this.mEglBase.getSurfaceHeight(this.mEglSurface));
            } else {
                GLES10.glViewport(0, 0, this.mEglBase.getSurfaceWidth(this.mEglSurface), this.mEglBase.getSurfaceHeight(this.mEglSurface));
            }
        }


        public void swap() {
            this.mEglBase.swap(this.mEglSurface);
        }


        public void swap(long presentationTimeNs) {
            this.mEglBase.swap(this.mEglSurface, presentationTimeNs);
        }


        public IContext getContext() {
            return this.mEglBase.getContext();
        }


        public void setPresentationTime(long presentationTimeNs) {
        }


        public boolean isValid() {

            return (this.mEglSurface != null && this.mEglSurface != EGL10.EGL_NO_SURFACE && this.mEglBase.
                    getSurfaceWidth(this.mEglSurface) > 0 && this.mEglBase.getSurfaceHeight(this.mEglSurface) > 0);
        }


        public void release() {
            this.mEglBase.makeDefault();
            this.mEglBase.destroyWindowSurface(this.mEglSurface);
            this.mEglSurface = EGL10.EGL_NO_SURFACE;
        }
    }


    public EGLBase10(int maxClientVersion, Context sharedContext, boolean withDepthBuffer, int stencilBits, boolean isRecordable) {
        init(maxClientVersion, sharedContext, withDepthBuffer, stencilBits, isRecordable);
    }


    public void release() {
        destroyContext();
        this.mContext = EGL_NO_CONTEXT;
        if (this.mEgl == null)
            return;
        this.mEgl.eglMakeCurrent(this.mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

        this.mEgl.eglTerminate(this.mEglDisplay);
        this.mEglDisplay = null;
        this.mEglConfig = null;
        this.mEgl = null;
    }


    public EglSurface createFromSurface(Object nativeWindow) {
        EglSurface eglSurface = new EglSurface(this, nativeWindow);
        eglSurface.makeCurrent();
        return eglSurface;
    }


    public EglSurface createOffscreen(int width, int height) {
        EglSurface eglSurface = new EglSurface(this, width, height);
        eglSurface.makeCurrent();
        return eglSurface;
    }


    public Context getContext() {
        return this.mContext;
    }


    public Config getConfig() {
        return this.mEglConfig;
    }


    public void makeDefault() {

        if (!this.mEgl.eglMakeCurrent(this.mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)) {
            Log.w("EGLBase10", "makeDefault:eglMakeCurrent:err=" + this.mEgl.eglGetError());
        }
    }


    public void sync() {
        this.mEgl.eglWaitGL();
        this.mEgl.eglWaitNative(12379, null);
    }


    public String queryString(int what) {
        return this.mEgl.eglQueryString(this.mEglDisplay, what);
    }


    public int getGlVersion() {
        return this.mGlVersion;
    }


    private final void init(int maxClientVersion, @Nullable Context sharedContext, boolean withDepthBuffer, int stencilBits, boolean isRecordable) {
        sharedContext = (sharedContext != null) ? sharedContext : EGL_NO_CONTEXT;
        if (this.mEgl == null) {
            this.mEgl = (EGL10) EGLContext.getEGL();
            this.mEglDisplay = this.mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            if (this.mEglDisplay == EGL10.EGL_NO_DISPLAY) {
                throw new RuntimeException("eglGetDisplay failed");
            }

            int[] version = new int[2];
            if (!this.mEgl.eglInitialize(this.mEglDisplay, version)) {
                this.mEglDisplay = null;
                throw new RuntimeException("eglInitialize failed");
            }
        }

        if (maxClientVersion >= 3) {

            EGLConfig config = getConfig(3, withDepthBuffer, stencilBits, isRecordable);
            if (config != null) {
                EGLContext context = createContext(sharedContext, config, 3);
                if (this.mEgl.eglGetError() == 12288) {

                    this.mEglConfig = new Config(config);
                    this.mContext = new Context(context);
                    this.mGlVersion = 3;
                }
            }
        }


        if (maxClientVersion >= 2 && (this.mContext == null || this.mContext.eglContext == EGL10.EGL_NO_CONTEXT)) {
            EGLConfig config = getConfig(2, withDepthBuffer, stencilBits, isRecordable);
            if (config == null) {
                throw new RuntimeException("chooseConfig failed");
            }

            try {
                EGLContext context = createContext(sharedContext, config, 2);
                checkEglError("eglCreateContext");
                this.mEglConfig = new Config(config);
                this.mContext = new Context(context);
                this.mGlVersion = 2;

            } catch (Exception e) {
                if (isRecordable) {
                    config = getConfig(2, withDepthBuffer, stencilBits, false);
                    if (config == null) {
                        throw new RuntimeException("chooseConfig failed");
                    }

                    EGLContext context = createContext(sharedContext, config, 2);
                    checkEglError("eglCreateContext");
                    this.mEglConfig = new Config(config);
                    this.mContext = new Context(context);
                    this.mGlVersion = 2;
                }
            }
        }
        if (this.mContext == null || this.mContext.eglContext == EGL10.EGL_NO_CONTEXT) {
            EGLConfig config = getConfig(1, withDepthBuffer, stencilBits, isRecordable);
            if (config == null) {
                throw new RuntimeException("chooseConfig failed");
            }

            EGLContext context = createContext(sharedContext, config, 1);
            checkEglError("eglCreateContext");
            this.mEglConfig = new Config(config);
            this.mContext = new Context(context);
            this.mGlVersion = 1;
        }

        int[] values = new int[1];
        this.mEgl.eglQueryContext(this.mEglDisplay, this.mContext.eglContext, 12440, values);
        Log.d("EGLBase10", "EGLContext created, client version " + values[0]);
        makeDefault();
    }


    private final boolean makeCurrent(EGLSurface surface) {
        if (surface == null || surface == EGL10.EGL_NO_SURFACE) {
            int error = this.mEgl.eglGetError();
            if (error == 12299) {
                Log.e("EGLBase10", "makeCurrent:EGL_BAD_NATIVE_WINDOW");
            }
            return false;
        }


        if (!this.mEgl.eglMakeCurrent(this.mEglDisplay, surface, surface, this.mContext.eglContext)) {
            Log.w("TAG", "eglMakeCurrent" + this.mEgl.eglGetError());
            return false;
        }
        return true;
    }


    private final int swap(EGLSurface surface) {
        if (!this.mEgl.eglSwapBuffers(this.mEglDisplay, surface)) {
            int err = this.mEgl.eglGetError();

            return err;
        }
        return 12288;
    }


    private final int swap(EGLSurface surface, long ignored) {
        if (!this.mEgl.eglSwapBuffers(this.mEglDisplay, surface)) {
            int err = this.mEgl.eglGetError();

            return err;
        }
        return 12288;
    }


    private final EGLContext createContext(@NonNull Context sharedContext, EGLConfig config, int version) {
        int[] attrib_list = {12440, version, 12344};

        EGLContext context = this.mEgl.eglCreateContext(this.mEglDisplay, config, sharedContext.eglContext, attrib_list);

        return context;
    }


    private final void destroyContext() {
        if (!this.mEgl.eglDestroyContext(this.mEglDisplay, this.mContext.eglContext)) {

            Log.e("destroyContext", "display:" + this.mEglDisplay + " context: " + this.mContext.eglContext);
            Log.e("EGLBase10", "eglDestroyContext:" + this.mEgl.eglGetError());
        }
        this.mContext = EGL_NO_CONTEXT;
    }

    private final int getSurfaceWidth(EGLSurface surface) {
        int[] value = new int[1];
        boolean ret = this.mEgl.eglQuerySurface(this.mEglDisplay, surface, 12375, value);
        if (!ret) value[0] = 0;
        return value[0];
    }

    private final int getSurfaceHeight(EGLSurface surface) {
        int[] value = new int[1];
        boolean ret = this.mEgl.eglQuerySurface(this.mEglDisplay, surface, 12374, value);
        if (!ret) value[0] = 0;
        return value[0];
    }


    private final EGLSurface createWindowSurface(Object nativeWindow) {
        int[] surfaceAttribs = {12344};


        EGLSurface result = null;
        try {

            result = this.mEgl.eglCreateWindowSurface(this.mEglDisplay, this.mEglConfig.eglConfig, nativeWindow, surfaceAttribs);
            if (result == null || result == EGL10.EGL_NO_SURFACE) {
                int error = this.mEgl.eglGetError();
                if (error == 12299) {
                    Log.e("EGLBase10", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
                }
                throw new RuntimeException("createWindowSurface failed error=" + error);
            }
            makeCurrent(result);
        } catch (Exception e) {
            Log.e("EGLBase10", "eglCreateWindowSurface", e);
            throw new IllegalArgumentException(e);
        }
        return result;
    }


    private final EGLSurface createOffscreenSurface(int width, int height) {
        int[] surfaceAttribs = {12375, width, 12374, height, 12344};


        this.mEgl.eglWaitGL();
        EGLSurface result = null;
        try {

            result = this.mEgl.eglCreatePbufferSurface(this.mEglDisplay, this.mEglConfig.eglConfig, surfaceAttribs);
            checkEglError("eglCreatePbufferSurface");
            if (result == null) {
                throw new RuntimeException("surface was null");
            }

        } catch (IllegalArgumentException e) {
            Log.e("EGLBase10", "createOffscreenSurface", e);

        } catch (RuntimeException e) {
            Log.e("EGLBase10", "createOffscreenSurface", e);
        }
        return result;
    }


    private final void destroyWindowSurface(EGLSurface surface) {
        if (surface != EGL10.EGL_NO_SURFACE) {

            this.mEgl.eglMakeCurrent(this.mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

            this.mEgl.eglDestroySurface(this.mEglDisplay, surface);
        }
        surface = EGL10.EGL_NO_SURFACE;
    }


    private final void checkEglError(String msg) {
        int error;
        if ((error = this.mEgl.eglGetError()) != 12288) {
            throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
        }
    }

    private final EGLConfig getConfig(int version, boolean hasDepthBuffer, int stencilBits, boolean isRecordable) {
        int renderableType = 4;
        if (version >= 3) {
            renderableType |= 0x40;
        }


        int[] attribList = {12352, renderableType, 12324, 8, 12323, 8, 12322, 8, 12321, 8, 12344, 12344, 12344, 12344, 12344, 12344, 12344};


        int offset = 10;
        if (stencilBits > 0) {
            attribList[offset++] = 12326;
            attribList[offset++] = 8;
        }
        if (hasDepthBuffer) {
            attribList[offset++] = 12325;
            attribList[offset++] = 16;
        }
        if (isRecordable && BuildCheck.isAndroid4_3()) {
            attribList[offset++] = 12610;
            attribList[offset++] = 1;
        }
        for (int i = attribList.length - 1; i >= offset; i--) {
            attribList[i] = 12344;
        }
        EGLConfig config = internalGetConfig(attribList);
        if (config == null && version == 2 &&
                isRecordable) {

            int n = attribList.length;
            for (int i = 10; i < n - 1; i += 2) {
                if (attribList[i] == 12610) {
                    for (int j = i; j < n; j++) {
                        attribList[j] = 12344;
                    }
                    break;
                }
            }
            config = internalGetConfig(attribList);
        }

        if (config == null) {
            Log.w("EGLBase10", "try to fallback to RGB565");
            attribList[3] = 5;
            attribList[5] = 6;
            attribList[7] = 5;
            config = internalGetConfig(attribList);
        }
        return config;
    }

    private EGLConfig internalGetConfig(int[] attribList) {
        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfigs = new int[1];

        if (!this.mEgl.eglChooseConfig(this.mEglDisplay, attribList, configs, configs.length, numConfigs)) {
            return null;
        }
        return configs[0];
    }
}

